package com.mb.framework.util.property;

import java.beans.PropertyDescriptor;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.sql.Blob;
import java.text.SimpleDateFormat;

import org.apache.commons.beanutils.PropertyUtils;

import com.mb.framework.util.DateTimeConstants;

public class PropertyUtilExt
{
	private static final int READ_BUFFER_LENGTH = 2048; // 2Kb

	private static final SimpleDateFormat TIMESTAMP_FORMATTER = new SimpleDateFormat(DateTimeConstants.DATE_PATTERN_LONG_YEAR);

	private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat(DateTimeConstants.DATE_PATTERN_LONG_YEAR);

	private static final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HHH:mm:ss");

	/**
	 * Copy property values from the origin bean to the destination bean for all
	 * cases where the property names are the same. For each property, a
	 * conversion is attempted as necessary. All combinations of standard
	 * JavaBeans and DynaBeans as origin and destination are supported.
	 * Properties that exist in the origin bean, but do not exist in the
	 * destination bean (or are read-only in the destination bean) are silently
	 * ignored.
	 * <p>
	 * In addition to the method with the same name in the
	 * <code>org.apache.commons.beanutils.PropertyUtils</code> class this method
	 * can also copy properties of the following types:
	 * <ul>
	 * <li>java.lang.Integer</li>
	 * <li>java.lang.Double</li>
	 * <li>java.lang.Long</li>
	 * <li>java.lang.Short</li>
	 * <li>java.lang.Float</li>
	 * <li>java.lang.String</li>
	 * <li>java.lang.Boolean</li>
	 * <li>java.sql.Date</li>
	 * <li>java.sql.Time</li>
	 * <li>java.sql.Timestamp</li>
	 * <li>java.math.BigDecimal</li>
	 * <li>a container-managed relations field.</li>
	 * </ul>
	 * 
	 * @param dest
	 *            Destination bean whose properties are modified
	 * @param orig
	 *            Origin bean whose properties are retrieved
	 * @throws IllegalAccessException
	 *             if the caller does not have access to the property accessor
	 *             method
	 * @throws InvocationTargetException
	 *             if the property accessor method throws an exception
	 * @throws NoSuchMethodException
	 *             if an accessor method for this propety cannot be found
	 * @throws ClassNotFoundException
	 *             if an incorrect relations class mapping exists.
	 * @throws InstantiationException
	 *             if an object of the mapped relations class can not be
	 *             constructed.
	 */
	public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, Exception
	{
		PropertyDescriptor origDescriptors[] = PropertyUtils.getPropertyDescriptors(orig);

		for (int i = 0; i < origDescriptors.length; i++)
		{
			String name = origDescriptors[i].getName();

			if (PropertyUtils.getPropertyDescriptor(dest, name) != null)
			{
				Object origValue = PropertyUtils.getSimpleProperty(orig, name);
				String origParamType = origDescriptors[i].getPropertyType().getName();
				try
				{
					// edited
					// if (origValue == null)throw new NullPointerException();

					PropertyUtils.setSimpleProperty(dest, name, origValue);
				}
				catch (Exception e)
				{
					try
					{
						String destParamType = PropertyUtils.getPropertyType(dest, name).getName();

						if (origValue instanceof String)
						{
							if (destParamType.equals("java.lang.Integer"))
							{
								Integer intValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									intValue = new Integer(sValue);
								}
								PropertyUtils.setSimpleProperty(dest, name, intValue);
							}
							else if (destParamType.equals("java.lang.Byte"))
							{
								Byte byteValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									byteValue = new Byte(sValue);
								}
								PropertyUtils.setSimpleProperty(dest, name, byteValue);
							}
							else if (destParamType.equals("java.lang.Double"))
							{
								Double doubleValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									doubleValue = new Double(sValue);
								}
								PropertyUtils.setSimpleProperty(dest, name, doubleValue);
							}
							else if (destParamType.equals("java.lang.Long"))
							{
								Long longValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									longValue = new Long(sValue);
								}
								PropertyUtils.setSimpleProperty(dest, name, longValue);
							}
							else if (destParamType.equals("java.lang.Short"))
							{
								Short shortValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									shortValue = new Short(sValue);
								}
								PropertyUtils.setSimpleProperty(dest, name, shortValue);
							}
							else if (destParamType.equals("java.lang.Float"))
							{
								Float floatValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									floatValue = new Float(sValue);
								}
								PropertyUtils.setSimpleProperty(dest, name, floatValue);
							}
							else if (destParamType.equals("java.sql.Date"))
							{
								java.sql.Date dateValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									dateValue = new java.sql.Date(DATE_FORMATTER.parse(sValue).getTime());
								}
								PropertyUtils.setSimpleProperty(dest, name, dateValue);
							}
							else if (destParamType.equals("java.sql.Time"))
							{
								java.sql.Time dateValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									dateValue = new java.sql.Time(TIME_FORMATTER.parse(sValue).getTime());
								}
								PropertyUtils.setSimpleProperty(dest, name, dateValue);
							}
							else if (destParamType.equals("java.sql.Timestamp"))
							{
								java.sql.Timestamp dateValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									dateValue = new java.sql.Timestamp(TIMESTAMP_FORMATTER.parse(sValue).getTime());
								}
								PropertyUtils.setSimpleProperty(dest, name, dateValue);
							}
							else if (destParamType.equals("java.lang.Boolean"))
							{
								Boolean bValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									bValue = Boolean.valueOf(sValue);
								}
								PropertyUtils.setSimpleProperty(dest, name, bValue);
							}
							else if (destParamType.equals("java.math.BigDecimal"))
							{
								BigDecimal bdValue = null;
								String sValue = ((String) origValue).trim();

								if (sValue.length() > 0)
								{
									bdValue = new BigDecimal(sValue);
								}
								PropertyUtils.setSimpleProperty(dest, name, bdValue);
							}
						}
						else if ((origValue != null) && (destParamType.equals("java.lang.String")))
						{
							// we're transferring a business-layer value object
							// into a String-based Struts form bean..
							if ("java.sql.Date".equals(origParamType))
							{
								PropertyUtils.setSimpleProperty(dest, name, DATE_FORMATTER.format(origValue));
							}
							else if ("java.sql.Timestamp".equals(origParamType))
							{
								PropertyUtils.setSimpleProperty(dest, name, TIMESTAMP_FORMATTER.format(origValue));
							}
							else if ("java.sql.Blob".equals(origParamType))
							{
								// convert a Blob to a String..
								Blob blob = (Blob) origValue;
								BufferedInputStream bin = null;
								try
								{
									int bytesRead;
									StringBuffer result = new StringBuffer();
									byte[] buffer = new byte[READ_BUFFER_LENGTH];
									bin = new BufferedInputStream(blob.getBinaryStream());
									do
									{
										bytesRead = bin.read(buffer);
										if (bytesRead != -1)
										{
											result.append(new String(buffer, 0, bytesRead));
										}
									}
									while (bytesRead == READ_BUFFER_LENGTH);

									PropertyUtils.setSimpleProperty(dest, name, result.toString());

								}
								finally
								{
									if (bin != null) try
									{
										bin.close();
									}
									catch (IOException ignored)
									{
									}
								}

							}
							else
							{
								PropertyUtils.setSimpleProperty(dest, name, origValue.toString());
							}
						}
					}
					catch (Exception e2)
					{
						throw e2;
					}
				}
			}
		}
	}

	public static boolean equalsSimpleProperties(Object mainObject, Object otherObject) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
	{
		PropertyDescriptor origDescriptors[] = PropertyUtils.getPropertyDescriptors(mainObject);
		for (int i = 0; i < origDescriptors.length; i++)
		{
			String name = origDescriptors[i].getName();

			if (PropertyUtils.getPropertyDescriptor(otherObject, name) != null)
			{
				Object origValue = PropertyUtils.getSimpleProperty(mainObject, name);
				Object otherValue = PropertyUtils.getSimpleProperty(otherObject, name);

				if (origValue != null && otherValue == null)
				{
					return false;
				}

				if (origValue == null && otherValue != null)
				{
					return false;
				}

				if (origValue != null && otherValue != null)
				{
					/*
					 * to walk around timestamp and date equals behavior Date d
					 * = new Date(); Timestamp t = new Timestamp(d.getTime());
					 * d.equals(t) > == true; t.equals(d) > == false;
					 */
					if (origValue instanceof java.sql.Timestamp && (otherValue instanceof java.sql.Date || otherValue instanceof java.util.Date))
					{
						if (!otherValue.equals(origValue))
						{
							return false;
						}
					}
					else
					{
						if (!origValue.equals(otherValue))
						{
							return false;
						}
					}
				}
			}
		}
		return true;
	}

	/**
	 * Resets the bean's <code>String</code> type properties to empty quotes.
	 * 
	 * @param orig
	 *            Origin bean whose properties are retrieved.
	 */
	public static void resetProperties(Object orig)
	{
		resetProperties(orig, "");
	}

	/**
	 * Resets the bean's <code>String</code> type properties to a specified
	 * value.
	 * 
	 * @param orig
	 *            Origin bean whose properties are retrieved.
	 * @param value
	 *            the value to set the bean properties to.
	 */
	public static void resetProperties(Object orig, String value)
	{
		PropertyDescriptor origDescriptors[] = null;

		try
		{
			origDescriptors = PropertyUtils.getPropertyDescriptors(orig);
		}
		catch (Exception e)
		{
			return;
		}

		if (origDescriptors != null)
		{
			for (int i = 0; i < origDescriptors.length; i++)
			{
				try
				{
					String name = origDescriptors[i].getName();
					Object orgValue = PropertyUtils.getSimpleProperty(orig, name);
					if ((orgValue != null) && (orgValue instanceof String))
					{
						PropertyUtils.setSimpleProperty(orig, name, value);
					}
				}
				catch (Exception e)
				{
					// skip it
				}
			}
		}
	}
}
